Skip to content

Add FP8×INT4 rowwise GEMM for ROCm (f8i4bf16_rowwise) - #495

Closed
liligwu wants to merge 2 commits into
meta-pytorch:mainfrom
liligwu:rocm/f8i4bf16-rowwise-v2
Closed

Add FP8×INT4 rowwise GEMM for ROCm (f8i4bf16_rowwise)#495
liligwu wants to merge 2 commits into
meta-pytorch:mainfrom
liligwu:rocm/f8i4bf16-rowwise-v2

Conversation

@liligwu

@liligwu liligwu commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

This PR is a copy of apicciau#3

Summary

Adds a Triton implementation of f8i4bf16_rowwise (FP8 activations × INT4 weights → BF16 output) for AMD MI300X/MI350X. The kernel reuses the unified _bf16i4_rowwise_kernel from the BF16×INT4 path, gated on a compile-time HAS_X_SCALE constexpr — no kernel fork.

Context

Implementation

Shared kernel with HAS_X_SCALE constexpr. The existing _bf16i4_rowwise_kernel gains two new parameters: x_scale_ptr (per-row FP8 activation dequant scale) and HAS_X_SCALE: tl.constexpr = False. When HAS_X_SCALE=False (BF16 path), the kernel behaves identically to before — the default value preserves backward compatibility and the dummy pointer is never dereferenced.

Deferred BF16 upcast. The .to(tl.bfloat16) cast on activation loads is moved from the load site to a single centralised location after the four masking branches. This is required because FP8 inputs return a native FP8 tensor type from tl.load that must be upcast explicitly — but for BF16 inputs the cast is a no-op, so both paths share the same code.

Two-dot path for FP8. When HAS_X_SCALE=True, the kernel issues two separate tl.dot calls (one for even-K, one for odd-K) instead of fusing via tl.cat. The tl.cat fusion concatenates tiles in LDS before the dot, which causes bank conflicts that are a net loss for FP8 activations on MI300X. Two separate dots avoid the LDS staging at the cost of two MFMA instructions — a net win for this dtype. The BF16 path retains the fused tl.cat dot (controlled by FUSE_DOT).

Per-row activation scale epilogue. After the K-loop, the float32 accumulator is multiplied element-wise by x_scale[m] before storing to the workspace. This fuses the FP8 dequantisation scale into the GEMM epilogue rather than requiring a separate pointwise kernel.

FP8 autotuner pruning. Four additional prune rules fire only when HAS_X_SCALE=True: skip BLOCK_N < 128 for large N, skip BLOCK_K > 64, skip SPLIT_K == 2, fix GROUP_SIZE_M = 4. These were determined by profiling winning configs on MI350X and reduce the FP8 autotuning search space by ~5×.

Thin Python wrapper. f8i4bf16_rowwise_gemm.py byte-splits FP8 activations into even/odd K columns, reinterprets them as FP8 TensorWrappers via reinterpret_fp8_type, and calls the shared kernel with HAS_X_SCALE=True. The wrapper also coerces scale/zp tensors to float32. Registered as mslk::f8i4bf16_rowwise on ROCm via torch.library.impl.

Testing

All 9 existing BF16×INT4 accuracy tests pass (no regressions):

9 passed in 74s

FP8×INT4 accuracy against dequantised reference (deq(FP8_x) @ deq(INT4_w)):

Shape M N K max error mean error
Small 1 256 1024 0.0005 0.0001
Medium 16 2048 1024 0.0020 0.0001
Large 512 4096 2048 0.0039 0.0002

FP8×INT4 dispatch test (torch.ops.mslk.f8i4bf16_rowwise vs direct call): bitwise identical on all shapes.

Performance

Hardware: AMD Instinct MI350X (gfx950), ROCm 7.1.1, Triton 3.5.1+rocm7.1.1.
triton.testing.do_bench, warmup=25, rep=200.
Peak: 4600 TFLOPS FP8, 2300 TFLOPS BF16, 8000 GB/s HBM.

f8i4bf16_rowwise vs bf16i4bf16_rowwise — decode shapes (memory-bandwidth bound)

Model-attributed shapes. FP8 and BF16 paths are both kernel-launch-latency bound at small M; performance converges.

Model M N K FP8 (μs) BF16 (μs) FP8 TFLOPS BF16 TFLOPS
llama4 1 896 5120 46.3 44.8 0.2 0.2
llama4 16 896 5120 45.4 44.8 3.2 3.3
llama4 64 896 5120 45.7 44.3 12.9 13.2
llama4 128 2048 5120 46.1 44.3 58.2 60.6
llama3_70b 1 8192 3584 40.6 38.8 1.4 1.5
llama3_70b 16 8192 3584 40.0 38.3 23.5 24.5
llama3_70b 64 8192 3584 41.5 40.7 90.6 92.4
llama3_70b 128 7168 8192 84.6 87.9 177.7 171.0

f8i4bf16_rowwise vs bf16i4bf16_rowwise — prefill shapes (compute bound)

FP8 MFMA has 2× the throughput of BF16 MFMA. The FP8 path is 5–8% faster at large M.

Model M N K FP8 (μs) BF16 (μs) FP8 TFLOPS % FP8 peak BF16 TFLOPS % BF16 peak
llama3_70b 2048 8192 3584 210.6 218.2 571.1 12.4% 551.1 24.0%
llama3_70b 2048 7168 8192 396.6 428.2 606.4 13.2% 561.7 24.4%
llama3_70b 4096 7168 8192 768.4 826.0 626.1 13.6% 582.4 25.3%

* Extend _bf16i4_rowwise_kernel with HAS_X_SCALE constexpr for FP8 activations
* Add per-row activation scale epilogue, deferred BF16 upcast, FP8 autotuner pruning
* New f8i4bf16_rowwise_gemm.py wrapper: FP8 byte-split, reinterpret, kernel call
* Register mslk::f8i4bf16_rowwise torch op on ROCm via torch.library.impl
* Add TritonFP8Int4Rowwise benchmark class and stub schemas for python_only mode
@meta-codesync

meta-codesync Bot commented Aug 21, 2026

Copy link
Copy Markdown

@q10 has imported this pull request. If you are a Meta employee, you can view this in D116965755.

…ion.

Co-authored-by: Cursor <cursoragent@cursor.com>
meta-codesync Bot pushed a commit that referenced this pull request Aug 25, 2026
Summary:
Pull Request resolved: #502

NOTE: No linked task. Please associate a task with this diff.

Adds a ROCm Triton implementation of `mslk::f8i4bf16_rowwise` for FP8 activations and packed INT4 weights while reusing the existing BF16×INT4 kernel.

- Defines the operator schema on both CUDA and ROCm so the HIP Python implementation can register reliably.
- Specializes the shared kernel with `HAS_X_SCALE`, two BF16 dot operations for FP8-backed inputs, and fused per-row activation scaling.
- Adds FP8-specific autotune candidates and pruning while preserving the prior BF16 tuning space.
- Validates tensor shapes, dtypes, devices, grouping invariants, and contiguous scale layouts.
- Avoids allocating unused split-K workspace for large-M shapes.
- Registers the ROCm benchmark implementation and adds a ROCm correctness/dispatch regression test.

Upstream PR: #495

Reviewed By: jwfromm

Differential Revision: D116965755

fbshipit-source-id: 9d790e148a8e400dbfa1a3f41be627ca140aa8b1
@q10

q10 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Merged in #502

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants